/*
 *
 * Copyright � 1999 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#ifdef HAVE_CONFIG_H
#include <kdrive-config.h>
#endif
#include "kdrive.h"
#include "inputstr.h"

#define XK_PUBLISHING
#include <X11/keysym.h>
#if HAVE_X11_XF86KEYSYM_H
#include <X11/XF86keysym.h>
#endif
#include "kkeymap.h"
#include <signal.h>
#include <stdio.h>

static DeviceIntPtr pKdKeyboard, pKdPointer;

#define MAX_MOUSE_DRIVERS   4

static const KdMouseFuncs *kdMouseFuncs[MAX_MOUSE_DRIVERS];

static int kdNMouseFuncs;
static const KdKeyboardFuncs *kdKeyboardFuncs;

static int kdBellPitch;
static int kdBellDuration;

static int kdLeds;

static Bool kdInputEnabled;

static Bool kdOffScreen;
static unsigned long kdOffScreenTime;

static KdMouseMatrix kdMouseMatrix = {
	{{1, 0, 0},
	 {0, 1, 0}}
};

static int kdMouseButtonCount;

int kdMinScanCode;
int kdMaxScanCode;

static int kdMinKeyCode;
static int kdMaxKeyCode;

static const int kdKeymapWidth = KD_MAX_WIDTH;

KeySym kdKeymap[KD_MAX_LENGTH * KD_MAX_WIDTH];

static CARD8 kdModMap[MAP_LENGTH];
static KeySymsRec kdKeySyms;

void KdResetInputMachine(void);

#define KD_KEY_COUNT		248

CARD8 kdKeyState[KD_KEY_COUNT / 8];

#define IsKeyDown(key) ((kdKeyState[(key) >> 3] >> ((key) & 7)) & 1)

#define KD_MAX_INPUT_FDS    8

typedef struct _kdInputFd {
	int type;
	int fd;
	void (*read) (int fd, void *closure);
	int (*enable) (int fd, void *closure);
	void (*disable) (int fd, void *closure);
	void *closure;
} KdInputFd;

static KdInputFd kdInputFds[KD_MAX_INPUT_FDS];
static int kdNumInputFds;
static int kdInputTypeSequence;

static void KdSigio(int sig)
{
	int i;

	for (i = 0; i < kdNumInputFds; i++)
		(*kdInputFds[i].read) (kdInputFds[i].fd, kdInputFds[i].closure);
}

static void KdBlockSigio(void)
{
	sigset_t set;

	sigemptyset(&set);
	sigaddset(&set, SIGIO);
	sigprocmask(SIG_BLOCK, &set, 0);
}

static void KdUnblockSigio(void)
{
	sigset_t set;

	sigemptyset(&set);
	sigaddset(&set, SIGIO);
	sigprocmask(SIG_UNBLOCK, &set, 0);
}

#undef VERIFY_SIGIO
#ifdef VERIFY_SIGIO

void KdAssertSigioBlocked(char *where)
{
	sigset_t set, old;

	sigemptyset(&set);
	sigprocmask(SIG_BLOCK, &set, &old);
	if (!sigismember(&old, SIGIO))
		ErrorF("SIGIO not blocked at %s\n", where);
}

#else

#define KdAssertSigioBlocked(s)

#endif

static int kdnFds;

#ifdef FNONBLOCK
#define NOBLOCK FNONBLOCK
#else
#define NOBLOCK FNDELAY
#endif

static void KdNonBlockFd(int fd)
{
	int flags;

	flags = fcntl(fd, F_GETFL);
	flags |= FASYNC | NOBLOCK;
	fcntl(fd, F_SETFL, flags);
}

static void KdAddFd(int fd)
{
	struct sigaction act;

	sigset_t set;

	kdnFds++;
	fcntl(fd, F_SETOWN, getpid());
	KdNonBlockFd(fd);
	AddEnabledDevice(fd);
	memset(&act, '\0', sizeof act);
	act.sa_handler = KdSigio;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGIO);
	sigaddset(&act.sa_mask, SIGALRM);
	sigaddset(&act.sa_mask, SIGVTALRM);
	sigaction(SIGIO, &act, 0);
	sigemptyset(&set);
	sigprocmask(SIG_SETMASK, &set, 0);
}

static void KdRemoveFd(int fd)
{
	struct sigaction act;

	int flags;

	kdnFds--;
	RemoveEnabledDevice(fd);
	flags = fcntl(fd, F_GETFL);
	flags &= ~(FASYNC | NOBLOCK);
	fcntl(fd, F_SETFL, flags);
	if (kdnFds == 0) {
		memset(&act, '\0', sizeof act);
		act.sa_handler = SIG_IGN;
		sigemptyset(&act.sa_mask);
		sigaction(SIGIO, &act, 0);
	}
}

int KdAllocInputType(void)
{
	return ++kdInputTypeSequence;
}

Bool
KdRegisterFd(int type, int fd, void (*read) (int fd, void *closure),
	     void *closure)
{
	if (kdNumInputFds == KD_MAX_INPUT_FDS)
		return FALSE;
	kdInputFds[kdNumInputFds].type = type;
	kdInputFds[kdNumInputFds].fd = fd;
	kdInputFds[kdNumInputFds].read = read;
	kdInputFds[kdNumInputFds].enable = 0;
	kdInputFds[kdNumInputFds].disable = 0;
	kdInputFds[kdNumInputFds].closure = closure;
	++kdNumInputFds;
	if (kdInputEnabled)
		KdAddFd(fd);
	return TRUE;
}

void
KdRegisterFdEnableDisable(int fd,
			  int (*enable) (int fd, void *closure),
			  void (*disable) (int fd, void *closure))
{
	int i;

	for (i = 0; i < kdNumInputFds; i++)
		if (kdInputFds[i].fd == fd) {
			kdInputFds[i].enable = enable;
			kdInputFds[i].disable = disable;
			break;
		}
}

void KdUnregisterFds(int type, Bool do_close)
{
	int i, j;

	for (i = 0; i < kdNumInputFds;) {
		if (kdInputFds[i].type == type) {
			if (kdInputEnabled)
				KdRemoveFd(kdInputFds[i].fd);
			if (do_close)
				close(kdInputFds[i].fd);
			--kdNumInputFds;
			for (j = i; j < kdNumInputFds; j++)
				kdInputFds[j] = kdInputFds[j + 1];
		} else
			i++;
	}
}

void KdDisableInput(void)
{
	int i;

	KdBlockSigio();

	for (i = 0; i < kdNumInputFds; i++) {
		KdRemoveFd(kdInputFds[i].fd);
		if (kdInputFds[i].disable)
			(*kdInputFds[i].disable) (kdInputFds[i].fd,
						  kdInputFds[i].closure);
	}
	kdInputEnabled = FALSE;
}

void KdEnableInput(void)
{
	xEvent xE;

	int i;

	kdInputEnabled = TRUE;
	for (i = 0; i < kdNumInputFds; i++) {
		KdNonBlockFd(kdInputFds[i].fd);
		if (kdInputFds[i].enable)
			kdInputFds[i].fd =
			    (*kdInputFds[i].enable) (kdInputFds[i].fd,
						     kdInputFds[i].closure);
		KdAddFd(kdInputFds[i].fd);
	}

	/* reset screen saver */
	xE.u.keyButtonPointer.time = GetTimeInMillis();
	NoticeEventTime(&xE);

	KdUnblockSigio();
}

static int KdMouseProc(DeviceIntPtr pDevice, int onoff)
{
	BYTE map[KD_MAX_BUTTON];

	DevicePtr pDev = (DevicePtr) pDevice;

	int i;

	if (!pDev)
		return BadImplementation;

	switch (onoff) {
	case DEVICE_INIT:
		for (i = 1; i <= kdMouseButtonCount; i++)
			map[i] = i;
		InitPointerDeviceStruct(pDev, map, kdMouseButtonCount,
					miPointerGetMotionEvents,
					(PtrCtrlProcPtr) NoopDDA,
					miPointerGetMotionBufferSize());
		break;

	case DEVICE_ON:
		pDev->on = TRUE;
		pKdPointer = pDevice;
		for (i = 0; i < kdNMouseFuncs; i++)
			(*kdMouseFuncs[i]->Init) ();
		break;
	case DEVICE_OFF:
	case DEVICE_CLOSE:
		if (pDev->on) {
			pDev->on = FALSE;
			pKdPointer = 0;
			for (i = 0; i < kdNMouseFuncs; i++)
				(*kdMouseFuncs[i]->Fini) ();
		}
		break;
	}
	return Success;
}

Bool LegalModifier(unsigned int key, DevicePtr pDev)
{
	return TRUE;
}

static void KdBell(int volume, DeviceIntPtr pDev, pointer ctrl, int something)
{
	if (kdInputEnabled)
		(*kdKeyboardFuncs->Bell) (volume, kdBellPitch, kdBellDuration);
}

static void KdSetLeds(void)
{
	if (kdInputEnabled)
		(*kdKeyboardFuncs->Leds) (kdLeds);
}

void KdSetLed(int led, Bool on)
{
	NoteLedState(pKdKeyboard, led, on);
	kdLeds = pKdKeyboard->kbdfeed->ctrl.leds;
	KdSetLeds();
}

void KdSetMouseMatrix(KdMouseMatrix * matrix)
{
	kdMouseMatrix = *matrix;
}

void
KdComputeMouseMatrix(KdMouseMatrix * m, Rotation randr, int width, int height)
{
	int x_dir = 1, y_dir = 1;

	int i, j;

	int size[2];

	size[0] = width;
	size[1] = height;
	if (randr & RR_Reflect_X)
		x_dir = -1;
	if (randr & RR_Reflect_Y)
		y_dir = -1;
	switch (randr & (RR_Rotate_All)) {
	case RR_Rotate_0:
		m->matrix[0][0] = x_dir;
		m->matrix[0][1] = 0;
		m->matrix[1][0] = 0;
		m->matrix[1][1] = y_dir;
		break;
	case RR_Rotate_90:
		m->matrix[0][0] = 0;
		m->matrix[0][1] = -x_dir;
		m->matrix[1][0] = y_dir;
		m->matrix[1][1] = 0;
		break;
	case RR_Rotate_180:
		m->matrix[0][0] = -x_dir;
		m->matrix[0][1] = 0;
		m->matrix[1][0] = 0;
		m->matrix[1][1] = -y_dir;
		break;
	case RR_Rotate_270:
		m->matrix[0][0] = 0;
		m->matrix[0][1] = x_dir;
		m->matrix[1][0] = -y_dir;
		m->matrix[1][1] = 0;
		break;
	}
	for (i = 0; i < 2; i++) {
		m->matrix[i][2] = 0;
		for (j = 0; j < 2; j++)
			if (m->matrix[i][j] < 0)
				m->matrix[i][2] = size[j] - 1;
	}
}

static void KdKbdCtrl(DeviceIntPtr pDevice, KeybdCtrl * ctrl)
{
	kdLeds = ctrl->leds;
	kdBellPitch = ctrl->bell_pitch;
	kdBellDuration = ctrl->bell_duration;
	KdSetLeds();
}

static int KdKeybdProc(DeviceIntPtr pDevice, int onoff)
{
	Bool ret;

	DevicePtr pDev = (DevicePtr) pDevice;

	if (!pDev)
		return BadImplementation;

	switch (onoff) {
	case DEVICE_INIT:
		if (pDev != LookupKeyboardDevice()) {
			return !Success;
		}
		ret = InitKeyboardDeviceStruct(pDev,
					       &kdKeySyms, kdModMap, KdBell,
					       KdKbdCtrl);
		if (!ret)
			return BadImplementation;
		break;
	case DEVICE_ON:
		pDev->on = TRUE;
		pKdKeyboard = pDevice;
		if (kdKeyboardFuncs)
			(*kdKeyboardFuncs->Init) ();
		break;
	case DEVICE_OFF:
	case DEVICE_CLOSE:
		pKdKeyboard = 0;
		if (pDev->on) {
			pDev->on = FALSE;
			if (kdKeyboardFuncs)
				(*kdKeyboardFuncs->Fini) ();
		}
		break;
	}
	return Success;
}

extern KeybdCtrl defaultKeyboardControl;

static void KdInitAutoRepeats(void)
{
	int key_code;

	unsigned char mask;

	int i;

	unsigned char *repeats;

	repeats = defaultKeyboardControl.autoRepeats;
	memset(repeats, '\0', 32);
	for (key_code = KD_MIN_KEYCODE; key_code <= KD_MAX_KEYCODE; key_code++) {
		if (!kdModMap[key_code]) {
			i = key_code >> 3;
			mask = 1 << (key_code & 7);
			repeats[i] |= mask;
		}
	}
}

static const KdKeySymModsRec kdKeySymMods[] = {
	{XK_Control_L, ControlMask},
	{XK_Control_R, ControlMask},
	{XK_Shift_L, ShiftMask},
	{XK_Shift_R, ShiftMask},
	{XK_Caps_Lock, LockMask},
	{XK_Shift_Lock, LockMask},
	{XK_Alt_L, Mod1Mask},
	{XK_Alt_R, Mod1Mask},
	{XK_Meta_L, Mod1Mask},
	{XK_Meta_R, Mod1Mask},
	{XK_Num_Lock, Mod2Mask},
	{XK_Super_L, Mod3Mask},
	{XK_Super_R, Mod3Mask},
	{XK_Hyper_L, Mod3Mask},
	{XK_Hyper_R, Mod3Mask},
	{XK_Mode_switch, Mod4Mask},
};

#define NUM_SYM_MODS (sizeof(kdKeySymMods) / sizeof(kdKeySymMods[0]))

static void KdInitModMap(void)
{
	int key_code;

	int row;

	int width;

	KeySym *syms;

	int i;

	width = kdKeySyms.mapWidth;
	for (key_code = kdMinKeyCode; key_code <= kdMaxKeyCode; key_code++) {
		kdModMap[key_code] = 0;
		syms = kdKeymap + (key_code - kdMinKeyCode) * width;
		for (row = 0; row < width; row++, syms++) {
			for (i = 0; i < NUM_SYM_MODS; i++) {
				if (*syms == kdKeySymMods[i].modsym)
					kdModMap[key_code] |=
					    kdKeySymMods[i].modbit;
			}
		}
	}
}

static void KdAddMouseDriver(const KdMouseFuncs * const pMouseFuncs)
{
	if (kdNMouseFuncs < MAX_MOUSE_DRIVERS)
		kdMouseFuncs[kdNMouseFuncs++] = pMouseFuncs;
}

void KdInitInput(const KdMouseFuncs * const pMouseFuncs,
			const KdKeyboardFuncs * const pKeyboardFuncs)
{
	DeviceIntPtr pKeyboard, pPointer;

	KdMouseInfo *mi;

	if (!kdMouseInfo)
		KdParseMouse(0);
	kdMouseButtonCount = 0;
	for (mi = kdMouseInfo; mi; mi = mi->next) {
		if (mi->nbutton > kdMouseButtonCount)
			kdMouseButtonCount = mi->nbutton;
	}

	kdNMouseFuncs = 0;
	KdAddMouseDriver(pMouseFuncs);
	kdKeyboardFuncs = pKeyboardFuncs;
	memset(kdKeyState, '\0', sizeof(kdKeyState));
	if (kdKeyboardFuncs)
		(*kdKeyboardFuncs->Load) ();
	kdMinKeyCode = kdMinScanCode + KD_KEY_OFFSET;
	kdMaxKeyCode = kdMaxScanCode + KD_KEY_OFFSET;
	kdKeySyms.map = kdKeymap;
	kdKeySyms.minKeyCode = kdMinKeyCode;
	kdKeySyms.maxKeyCode = kdMaxKeyCode;
	kdKeySyms.mapWidth = kdKeymapWidth;
	kdLeds = 0;
	kdBellPitch = 1000;
	kdBellDuration = 200;
	kdInputEnabled = TRUE;
	KdInitModMap();
	KdInitAutoRepeats();
	KdResetInputMachine();
	pPointer = AddInputDevice(KdMouseProc, TRUE);
	pKeyboard = AddInputDevice(KdKeybdProc, TRUE);
	RegisterPointerDevice(pPointer);
	RegisterKeyboardDevice(pKeyboard);
	miRegisterPointerDevice(screenInfo.screens[0], pPointer);
	mieqInit(&pKeyboard->public, &pPointer->public);
}

/*
 * Middle button emulation state machine
 *
 *  Possible transitions:
 *	Button 1 press	    v1
 *	Button 1 release    ^1
 *	Button 2 press	    v2
 *	Button 2 release    ^2
 *	Button 3 press	    v3
 *	Button 3 release    ^3
 *	Button other press  vo
 *	Button other release ^o
 *	Mouse motion	    <>
 *	Keyboard event	    k
 *	timeout		    ...
 *	outside box	    <->
 *
 *  States:
 *	start
 *	button_1_pend
 *	button_1_down
 *	button_2_down
 *	button_3_pend
 *	button_3_down
 *	synthetic_2_down_13
 *	synthetic_2_down_3
 *	synthetic_2_down_1
 *
 *  Transition diagram
 *
 *  start
 *	v1  -> (hold) (settimeout) button_1_pend
 *	^1  -> (deliver) start
 *	v2  -> (deliver) button_2_down
 *	^2  -> (deliever) start
 *	v3  -> (hold) (settimeout) button_3_pend
 *	^3  -> (deliver) start
 *	vo  -> (deliver) start
 *	^o  -> (deliver) start
 *	<>  -> (deliver) start
 *	k   -> (deliver) start
 *
 *  button_1_pend	(button 1 is down, timeout pending)
 *	^1  -> (release) (deliver) start
 *	v2  -> (release) (deliver) button_1_down
 *	^2  -> (release) (deliver) button_1_down
 *	v3  -> (cleartimeout) (generate v2) synthetic_2_down_13
 *	^3  -> (release) (deliver) button_1_down
 *	vo  -> (release) (deliver) button_1_down
 *	^o  -> (release) (deliver) button_1_down
 *	<-> -> (release) (deliver) button_1_down
 *	<>  -> (deliver) button_1_pend
 *	k   -> (release) (deliver) button_1_down
 *	... -> (release) button_1_down
 *
 *  button_1_down	(button 1 is down)
 *	^1  -> (deliver) start
 *	v2  -> (deliver) button_1_down
 *	^2  -> (deliver) button_1_down
 *	v3  -> (deliver) button_1_down
 *	^3  -> (deliver) button_1_down
 *	vo  -> (deliver) button_1_down
 *	^o  -> (deliver) button_1_down
 *	<>  -> (deliver) button_1_down
 *	k   -> (deliver) button_1_down
 *
 *  button_2_down	(button 2 is down)
 *	v1  -> (deliver) button_2_down
 *	^1  -> (deliver) button_2_down
 *	^2  -> (deliver) start
 *	v3  -> (deliver) button_2_down
 *	^3  -> (deliver) button_2_down
 *	vo  -> (deliver) button_2_down
 *	^o  -> (deliver) button_2_down
 *	<>  -> (deliver) button_2_down
 *	k   -> (deliver) button_2_down
 *
 *  button_3_pend	(button 3 is down, timeout pending)
 *	v1  -> (generate v2) synthetic_2_down
 *	^1  -> (release) (deliver) button_3_down
 *	v2  -> (release) (deliver) button_3_down
 *	^2  -> (release) (deliver) button_3_down
 *	^3  -> (release) (deliver) start
 *	vo  -> (release) (deliver) button_3_down
 *	^o  -> (release) (deliver) button_3_down
 *	<-> -> (release) (deliver) button_3_down
 *	<>  -> (deliver) button_3_pend
 *	k   -> (release) (deliver) button_3_down
 *	... -> (release) button_3_down
 *
 *  button_3_down	(button 3 is down)
 *	v1  -> (deliver) button_3_down
 *	^1  -> (deliver) button_3_down
 *	v2  -> (deliver) button_3_down
 *	^2  -> (deliver) button_3_down
 *	^3  -> (deliver) start
 *	vo  -> (deliver) button_3_down
 *	^o  -> (deliver) button_3_down
 *	<>  -> (deliver) button_3_down
 *	k   -> (deliver) button_3_down
 *
 *  synthetic_2_down_13	(button 1 and 3 are down)
 *	^1  -> (generate ^2) synthetic_2_down_3
 *	v2  -> synthetic_2_down_13
 *	^2  -> synthetic_2_down_13
 *	^3  -> (generate ^2) synthetic_2_down_1
 *	vo  -> (deliver) synthetic_2_down_13
 *	^o  -> (deliver) synthetic_2_down_13
 *	<>  -> (deliver) synthetic_2_down_13
 *	k   -> (deliver) synthetic_2_down_13
 *
 *  synthetic_2_down_3 (button 3 is down)
 *	v1  -> (deliver) synthetic_2_down_3
 *	^1  -> (deliver) synthetic_2_down_3
 *	v2  -> synthetic_2_down_3
 *	^2  -> synthetic_2_down_3
 *	^3  -> start
 *	vo  -> (deliver) synthetic_2_down_3
 *	^o  -> (deliver) synthetic_2_down_3
 *	<>  -> (deliver) synthetic_2_down_3
 *	k   -> (deliver) synthetic_2_down_3
 *
 *  synthetic_2_down_1 (button 1 is down)
 *	^1  -> start
 *	v2  -> synthetic_2_down_1
 *	^2  -> synthetic_2_down_1
 *	v3  -> (deliver) synthetic_2_down_1
 *	^3  -> (deliver) synthetic_2_down_1
 *	vo  -> (deliver) synthetic_2_down_1
 *	^o  -> (deliver) synthetic_2_down_1
 *	<>  -> (deliver) synthetic_2_down_1
 *	k   -> (deliver) synthetic_2_down_1
 */

typedef enum _inputClass {
	down_1, up_1,
	down_2, up_2,
	down_3, up_3,
	down_o, up_o,
	motion, outside_box,
	keyboard, timeout,
	num_input_class
} KdInputClass;

typedef enum __attribute__((packed)) _inputAction {
	noop,
	hold,
	setto,
	deliver,
	release,
	clearto,
	gen_down_2,
	gen_up_2
} KdInputAction;

#define MAX_ACTIONS 2

typedef struct _inputTransition {
	KdInputAction actions[MAX_ACTIONS];
	KdMouseState nextState;
} KdInputTransition;

static const
KdInputTransition kdInputMachine[num_input_states][num_input_class] = {
	/* start */
	{
	 {{hold, setto}, button_1_pend},	/* v1 */
	 {{deliver, noop}, start},	/* ^1 */
	 {{deliver, noop}, button_2_down},	/* v2 */
	 {{deliver, noop}, start},	/* ^2 */
	 {{hold, setto}, button_3_pend},	/* v3 */
	 {{deliver, noop}, start},	/* ^3 */
	 {{deliver, noop}, start},	/* vo */
	 {{deliver, noop}, start},	/* ^o */
	 {{deliver, noop}, start},	/* <> */
	 {{deliver, noop}, start},	/* <-> */
	 {{noop, noop}, start},	/* k */
	 {{noop, noop}, start},	/* ... */
	 },
	/* button_1_pend */
	{
	 {{noop, noop}, button_1_pend},	/* v1 */
	 {{release, deliver}, start},	/* ^1 */
	 {{release, deliver}, button_1_down},	/* v2 */
	 {{release, deliver}, button_1_down},	/* ^2 */
	 {{clearto, gen_down_2}, synth_2_down_13},	/* v3 */
	 {{release, deliver}, button_1_down},	/* ^3 */
	 {{release, deliver}, button_1_down},	/* vo */
	 {{release, deliver}, button_1_down},	/* ^o */
	 {{deliver, noop}, button_1_pend},	/* <> */
	 {{release, deliver}, button_1_down},	/* <-> */
	 {{noop, noop}, button_1_down},	/* k */
	 {{release, noop}, button_1_down},	/* ... */
	 },
	/* button_1_down */
	{
	 {{noop, noop}, button_1_down},	/* v1 */
	 {{deliver, noop}, start},	/* ^1 */
	 {{deliver, noop}, button_1_down},	/* v2 */
	 {{deliver, noop}, button_1_down},	/* ^2 */
	 {{deliver, noop}, button_1_down},	/* v3 */
	 {{deliver, noop}, button_1_down},	/* ^3 */
	 {{deliver, noop}, button_1_down},	/* vo */
	 {{deliver, noop}, button_1_down},	/* ^o */
	 {{deliver, noop}, button_1_down},	/* <> */
	 {{deliver, noop}, button_1_down},	/* <-> */
	 {{noop, noop}, button_1_down},	/* k */
	 {{noop, noop}, button_1_down},	/* ... */
	 },
	/* button_2_down */
	{
	 {{deliver, noop}, button_2_down},	/* v1 */
	 {{deliver, noop}, button_2_down},	/* ^1 */
	 {{noop, noop}, button_2_down},	/* v2 */
	 {{deliver, noop}, start},	/* ^2 */
	 {{deliver, noop}, button_2_down},	/* v3 */
	 {{deliver, noop}, button_2_down},	/* ^3 */
	 {{deliver, noop}, button_2_down},	/* vo */
	 {{deliver, noop}, button_2_down},	/* ^o */
	 {{deliver, noop}, button_2_down},	/* <> */
	 {{deliver, noop}, button_2_down},	/* <-> */
	 {{noop, noop}, button_2_down},	/* k */
	 {{noop, noop}, button_2_down},	/* ... */
	 },
	/* button_3_pend */
	{
	 {{clearto, gen_down_2}, synth_2_down_13},	/* v1 */
	 {{release, deliver}, button_3_down},	/* ^1 */
	 {{release, deliver}, button_3_down},	/* v2 */
	 {{release, deliver}, button_3_down},	/* ^2 */
	 {{release, deliver}, button_3_down},	/* v3 */
	 {{release, deliver}, start},	/* ^3 */
	 {{release, deliver}, button_3_down},	/* vo */
	 {{release, deliver}, button_3_down},	/* ^o */
	 {{deliver, noop}, button_3_pend},	/* <> */
	 {{release, deliver}, button_3_down},	/* <-> */
	 {{release, noop}, button_3_down},	/* k */
	 {{release, noop}, button_3_down},	/* ... */
	 },
	/* button_3_down */
	{
	 {{deliver, noop}, button_3_down},	/* v1 */
	 {{deliver, noop}, button_3_down},	/* ^1 */
	 {{deliver, noop}, button_3_down},	/* v2 */
	 {{deliver, noop}, button_3_down},	/* ^2 */
	 {{noop, noop}, button_3_down},	/* v3 */
	 {{deliver, noop}, start},	/* ^3 */
	 {{deliver, noop}, button_3_down},	/* vo */
	 {{deliver, noop}, button_3_down},	/* ^o */
	 {{deliver, noop}, button_3_down},	/* <> */
	 {{deliver, noop}, button_3_down},	/* <-> */
	 {{noop, noop}, button_3_down},	/* k */
	 {{noop, noop}, button_3_down},	/* ... */
	 },
	/* synthetic_2_down_13 */
	{
	 {{noop, noop}, synth_2_down_13},	/* v1 */
	 {{gen_up_2, noop}, synth_2_down_3},	/* ^1 */
	 {{noop, noop}, synth_2_down_13},	/* v2 */
	 {{noop, noop}, synth_2_down_13},	/* ^2 */
	 {{noop, noop}, synth_2_down_13},	/* v3 */
	 {{gen_up_2, noop}, synth_2_down_1},	/* ^3 */
	 {{deliver, noop}, synth_2_down_13},	/* vo */
	 {{deliver, noop}, synth_2_down_13},	/* ^o */
	 {{deliver, noop}, synth_2_down_13},	/* <> */
	 {{deliver, noop}, synth_2_down_13},	/* <-> */
	 {{noop, noop}, synth_2_down_13},	/* k */
	 {{noop, noop}, synth_2_down_13},	/* ... */
	 },
	/* synthetic_2_down_3 */
	{
	 {{deliver, noop}, synth_2_down_3},	/* v1 */
	 {{deliver, noop}, synth_2_down_3},	/* ^1 */
	 {{deliver, noop}, synth_2_down_3},	/* v2 */
	 {{deliver, noop}, synth_2_down_3},	/* ^2 */
	 {{noop, noop}, synth_2_down_3},	/* v3 */
	 {{noop, noop}, start},	/* ^3 */
	 {{deliver, noop}, synth_2_down_3},	/* vo */
	 {{deliver, noop}, synth_2_down_3},	/* ^o */
	 {{deliver, noop}, synth_2_down_3},	/* <> */
	 {{deliver, noop}, synth_2_down_3},	/* <-> */
	 {{noop, noop}, synth_2_down_3},	/* k */
	 {{noop, noop}, synth_2_down_3},	/* ... */
	 },
	/* synthetic_2_down_1 */
	{
	 {{noop, noop}, synth_2_down_1},	/* v1 */
	 {{noop, noop}, start},	/* ^1 */
	 {{deliver, noop}, synth_2_down_1},	/* v2 */
	 {{deliver, noop}, synth_2_down_1},	/* ^2 */
	 {{deliver, noop}, synth_2_down_1},	/* v3 */
	 {{deliver, noop}, synth_2_down_1},	/* ^3 */
	 {{deliver, noop}, synth_2_down_1},	/* vo */
	 {{deliver, noop}, synth_2_down_1},	/* ^o */
	 {{deliver, noop}, synth_2_down_1},	/* <> */
	 {{deliver, noop}, synth_2_down_1},	/* <-> */
	 {{noop, noop}, synth_2_down_1},	/* k */
	 {{noop, noop}, synth_2_down_1},	/* ... */
	 },
};

#define EMULATION_WINDOW    10
#define EMULATION_TIMEOUT   100

#define EventX(e)   ((e)->u.keyButtonPointer.rootX)
#define EventY(e)   ((e)->u.keyButtonPointer.rootY)

static int KdInsideEmulationWindow(KdMouseInfo * mi, xEvent * ev)
{
	if (ev->u.keyButtonPointer.pad1) {
		mi->emulationDx += EventX(ev);
		mi->emulationDy += EventY(ev);
	} else {
		mi->emulationDx = EventX(&mi->heldEvent) - EventX(ev);
		mi->emulationDy = EventY(&mi->heldEvent) - EventY(ev);
	}
	return (abs(mi->emulationDx) < EMULATION_WINDOW &&
		abs(mi->emulationDy) < EMULATION_WINDOW);
}

static KdInputClass KdClassifyInput(KdMouseInfo * mi, xEvent * ev)
{
	switch (ev->u.u.type) {
	case ButtonPress:
		switch (ev->u.u.detail) {
		case 1:
			return down_1;
		case 2:
			return down_2;
		case 3:
			return down_3;
		default:
			return down_o;
		}
		break;
	case ButtonRelease:
		switch (ev->u.u.detail) {
		case 1:
			return up_1;
		case 2:
			return up_2;
		case 3:
			return up_3;
		default:
			return up_o;
		}
		break;
	case MotionNotify:
		if (mi->eventHeld && !KdInsideEmulationWindow(mi, ev))
			return outside_box;
		else
			return motion;
	default:
		return keyboard;
	}
	return keyboard;
}

#ifndef NDEBUG
char *kdStateNames[] = {
	"start",
	"button_1_pend",
	"button_1_down",
	"button_2_down",
	"button_3_pend",
	"button_3_down",
	"synth_2_down_13",
	"synth_2_down_3",
	"synthetic_2_down_1",
	"num_input_states"
};

char *kdClassNames[] = {
	"down_1", "up_1",
	"down_2", "up_2",
	"down_3", "up_3",
	"motion", "ouside_box",
	"keyboard", "timeout",
	"num_input_class"
};

char *kdActionNames[] = {
	"noop",
	"hold",
	"setto",
	"deliver",
	"release",
	"clearto",
	"gen_down_2",
	"gen_up_2",
};
#endif

static void KdQueueEvent(xEvent * ev)
{
	KdAssertSigioBlocked("KdQueueEvent");
	if (ev->u.u.type == MotionNotify) {
		if (ev->u.keyButtonPointer.pad1) {
			ev->u.keyButtonPointer.pad1 = 0;
			miPointerDeltaCursor(ev->u.keyButtonPointer.rootX,
					     ev->u.keyButtonPointer.rootY,
					     ev->u.keyButtonPointer.time);
		} else {
			miPointerAbsoluteCursor(ev->u.keyButtonPointer.rootX,
						ev->u.keyButtonPointer.rootY,
						ev->u.keyButtonPointer.time);
		}
	} else {
		mieqEnqueue(ev);
	}
}

static void KdRunMouseMachine(KdMouseInfo * mi, KdInputClass c, xEvent * ev)
{
	const KdInputTransition *t;

	int a;

	t = &kdInputMachine[mi->mouseState][c];
	for (a = 0; a < MAX_ACTIONS; a++) {
		switch (t->actions[a]) {
		case noop:
			break;
		case hold:
			mi->eventHeld = TRUE;
			mi->emulationDx = 0;
			mi->emulationDy = 0;
			mi->heldEvent = *ev;
			break;
		case setto:
			mi->emulationTimeout =
			    GetTimeInMillis() + EMULATION_TIMEOUT;
			mi->timeoutPending = TRUE;
			break;
		case deliver:
			KdQueueEvent(ev);
			break;
		case release:
			mi->eventHeld = FALSE;
			mi->timeoutPending = FALSE;
			KdQueueEvent(&mi->heldEvent);
			break;
		case clearto:
			mi->timeoutPending = FALSE;
			break;
		case gen_down_2:
			ev->u.u.detail = 2;
			mi->eventHeld = FALSE;
			KdQueueEvent(ev);
			break;
		case gen_up_2:
			ev->u.u.detail = 2;
			KdQueueEvent(ev);
			break;
		}
	}
	mi->mouseState = t->nextState;
}

void KdResetInputMachine(void)
{
	KdMouseInfo *mi;

	for (mi = kdMouseInfo; mi; mi = mi->next) {
		mi->mouseState = start;
		mi->eventHeld = FALSE;
	}
}

static void KdHandleMouseEvent(KdMouseInfo * mi, xEvent * ev)
{
	if (mi->emulateMiddleButton)
		KdRunMouseMachine(mi, KdClassifyInput(mi, ev), ev);
	else
		KdQueueEvent(ev);
}

static void KdReceiveTimeout(KdMouseInfo * mi)
{
	KdRunMouseMachine(mi, timeout, 0);
}

#define KILL_SEQUENCE ((1L << KK_CONTROL)|(1L << KK_ALT)|(1L << KK_F8)|(1L << KK_F10))
#define SPECIAL_SEQUENCE ((1L << KK_CONTROL) | (1L << KK_ALT))
#define SETKILLKEY(b) (KdSpecialKeys |= (1L << (b)))
#define CLEARKILLKEY(b) (KdSpecialKeys &= ~(1L << (b)))
#define KEYMAP	    (pKdKeyboard->key->curKeySyms)
#define KEYCOL1(k) (KEYMAP.map[((k)-kdMinKeyCode)*KEYMAP.mapWidth])

/*
 * kdCheckTermination
 *
 * This function checks for the key sequence that terminates the server.  When
 * detected, it sets the dispatchException flag and returns.  The key sequence
 * is:
 *	Control-Alt
 * It's assumed that the server will be waken up by the caller when this
 * function returns.
 */

extern int nClients;

static void KdCheckSpecialKeys(xEvent * xE)
{
	KeySym sym = KEYCOL1(xE->u.u.detail);

	if (!pKdKeyboard)
		return;

	/*
	 * Ignore key releases
	 */

	if (xE->u.u.type == KeyRelease)
		return;

	/*
	 * Check for control/alt pressed
	 */
	if ((pKdKeyboard->key->state & (ControlMask | Mod1Mask)) !=
	    (ControlMask | Mod1Mask))
		return;

	/*
	 * Let OS function see keysym first
	 */

	if (kdOsFuncs->SpecialKey)
		if ((*kdOsFuncs->SpecialKey) (sym))
			return;

	/*
	 * Now check for backspace or delete; these signal the
	 * X server to terminate
	 */
	switch (sym) {
	case XK_BackSpace:
	case XK_Delete:
	case XK_KP_Delete:
		/*
		 * Set the dispatch exception flag so the server will terminate the
		 * next time through the dispatch loop.
		 */
		if (kdDontZap == FALSE)
			dispatchException |= DE_TERMINATE;
		break;
	}
}

/*
 * kdEnqueueKeyboardEvent
 *
 * This function converts hardware keyboard event information into an X event
 * and enqueues it using MI.  It wakes up the server before returning so that
 * the event will be processed normally.
 *
 */

static void KdHandleKeyboardEvent(xEvent * ev)
{
	int key = ev->u.u.detail;

	int byte;

	CARD8 bit;

	KdMouseInfo *mi;

	byte = key >> 3;
	bit = 1 << (key & 7);
	switch (ev->u.u.type) {
	case KeyPress:
		kdKeyState[byte] |= bit;
		break;
	case KeyRelease:
		kdKeyState[byte] &= ~bit;
		break;
	}
	for (mi = kdMouseInfo; mi; mi = mi->next)
		KdRunMouseMachine(mi, keyboard, 0);
	KdQueueEvent(ev);
}

void KdReleaseAllKeys(void)
{
	xEvent xE;

	int key;

	KdBlockSigio();
	for (key = 0; key < KD_KEY_COUNT; key++)
		if (IsKeyDown(key)) {
			xE.u.keyButtonPointer.time = GetTimeInMillis();
			xE.u.u.type = KeyRelease;
			xE.u.u.detail = key;
			KdHandleKeyboardEvent(&xE);
		}
	KdUnblockSigio();
}

static void KdCheckLock(void)
{
	KeyClassPtr keyc = pKdKeyboard->key;

	Bool isSet, shouldBeSet;

	if (kdKeyboardFuncs->LockLed) {
		isSet = (kdLeds & (1 << (kdKeyboardFuncs->LockLed - 1))) != 0;
		shouldBeSet = (keyc->state & LockMask) != 0;
		if (isSet != shouldBeSet) {
			KdSetLed(kdKeyboardFuncs->LockLed, shouldBeSet);
		}
	}
}

#define KEY_KP_0         /* 0           Insert    0x52  */   82
#define KEY_KP_1         /* 1           End       0x4f  */   79
#define KEY_KP_2         /* 2           Down      0x50  */   80
#define KEY_KP_3         /* 3           PgDown    0x51  */   81
#define KEY_KP_4         /* 4           Left      0x4b  */   75
#define KEY_KP_5         /* 5                     0x4c  */   76
#define KEY_KP_6         /* 6           Right     0x4d  */   77
#define KEY_KP_7         /* 7           Home      0x47  */   71
#define KEY_KP_8         /* 8           Up        0x48  */   72
#define KEY_KP_9         /* 9           PgUp      0x49  */   73
#define KEY_KP_Decimal   /* . (Decimal) Delete    0x53  */   83

#define KEY_Insert       /* Insert                0x62  */   0x6e
#define KEY_Delete       /* Delete                0x63  */   0x6f
#define KEY_Home         /* Home                  0x59  */   0x66
#define KEY_Up           /* Up                    0x5a  */   0x67
#define KEY_PgUp         /* PgUp                  0x5b  */   0x68
#define KEY_Left         /* Left                  0x5c  */   0x69
#define KEY_Right        /* Right                 0x5e  */   0x6a
#define KEY_End          /* End                   0x5f  */   0x6b
#define KEY_Down         /* Down                  0x60  */   0x6c
#define KEY_PgDown       /* PgDown                0x61  */   0x6d

static unsigned char remap(const unsigned char scan) {
	// The numpad buttons need to be remapped, as they have the same scan keys

	switch (scan) {
		case KEY_KP_0:
			return KEY_Insert;
		case KEY_KP_1:
			return KEY_End;
		case KEY_KP_2:
			return KEY_Down;
		case KEY_KP_3:
			return KEY_PgDown;
		case KEY_KP_4:
			return KEY_Left;
		case KEY_KP_6:
			return KEY_Right;
		case KEY_KP_7:
			return KEY_Home;
		case KEY_KP_8:
			return KEY_Up;
		case KEY_KP_9:
			return KEY_PgUp;
		case KEY_KP_Decimal:
			return KEY_Delete;
	}

	return scan;
}

void KdEnqueueKeyboardEvent(unsigned char scan_code, unsigned char is_up)
{
	unsigned char key_code;
	static unsigned int locks = 0;

	xEvent xE;
	KeyClassPtr keyc;

	if (!pKdKeyboard)
		return;

	keyc = pKdKeyboard->key;
	xE.u.keyButtonPointer.time = GetTimeInMillis();

	if (!(locks & Mod2Mask))
		scan_code = remap(scan_code);

	if (kdMinScanCode <= scan_code && scan_code <= kdMaxScanCode) {
		key_code = scan_code + KD_MIN_KEYCODE - kdMinScanCode;

		/*
		 * Set up this event -- the type may be modified below
		 */
		if (is_up)
			xE.u.u.type = KeyRelease;
		else
			xE.u.u.type = KeyPress;
		xE.u.u.detail = key_code;

		// Handle toggling keys
		if (xE.u.u.type == KeyPress) {
			switch (KEYCOL1(key_code)) {
				case XK_Num_Lock:
					locks ^= Mod2Mask;
				break;
				case XK_Shift_Lock:
				case XK_Caps_Lock:
					locks ^= LockMask;
				break;
			}
		}
		keyc->state |= locks;

		/*
		 * Check pressed keys which are already down
		 */
		if (IsKeyDown(key_code) && xE.u.u.type == KeyPress) {
			KeybdCtrl *ctrl = &pKdKeyboard->kbdfeed->ctrl;

			/*
			 * Check auto repeat
			 */
			if (!ctrl->autoRepeat || keyc->modifierMap[key_code] ||
			    !(ctrl->
			      autoRepeats[key_code >> 3] & (1 <<
							    (key_code & 7)))) {
				return;
			}
			/*
			 * X delivers press/release even for autorepeat
			 */
			xE.u.u.type = KeyRelease;
			KdHandleKeyboardEvent(&xE);
			xE.u.u.type = KeyPress;
		}
		/*
		 * Check released keys which are already up
		 */
		else if (!IsKeyDown(key_code) && xE.u.u.type == KeyRelease) {
			return;
		}
		KdCheckSpecialKeys(&xE);
		KdHandleKeyboardEvent(&xE);
	}
}

#define SetButton(mi, b, v, s) \
{\
    xE.u.u.detail = mi->map[b]; \
    xE.u.u.type = v; \
    KdHandleMouseEvent (mi, &xE); \
}

#define Press(mi, b)         SetButton(mi, b, ButtonPress, "Down")
#define Release(mi, b)       SetButton(mi, b, ButtonRelease, "Up")

/*
 * kdEnqueueMouseEvent
 *
 * This function converts hardware mouse event information into X event
 * information.  A mouse movement event is passed off to MI to generate
 * a MotionNotify event, if appropriate.  Button events are created and
 * passed off to MI for enqueueing.
 */

static void KdMouseAccelerate(DeviceIntPtr device, int *dx, int *dy)
{
	PtrCtrl *pCtrl = &device->ptrfeed->ctrl;

	double speed = sqrt(*dx * *dx + *dy * *dy);

	double accel;

#ifdef QUADRATIC_ACCELERATION
	double m;

	/*
	 * Ok, so we want it moving num/den times faster at threshold*2
	 *
	 * accel = m *threshold + b
	 * 1 = m * 0 + b    -> b = 1
	 *
	 * num/den = m * (threshold * 2) + 1
	 *
	 * num / den - 1 = m * threshold * 2
	 * (num / den - 1) / threshold * 2 = m
	 */
	m = (((double)pCtrl->num / (double)pCtrl->den - 1.0) /
	     ((double)pCtrl->threshold * 2.0));
	accel = m * speed + 1;
#else
	accel = 1.0;
	if (speed > pCtrl->threshold)
		accel = (double)pCtrl->num / pCtrl->den;
#endif
	*dx = accel * *dx;
	*dy = accel * *dy;
}

void KdEnqueueMouseEvent(KdMouseInfo * mi, unsigned long flags, int rx, int ry)
{
	CARD32 ms;

	xEvent xE;

	unsigned char buttons;

	int x, y;

	int (*matrix)[3] = kdMouseMatrix.matrix;

	unsigned long button;

	int n;

	if (!pKdPointer)
		return;

	ms = GetTimeInMillis();

	if (flags & KD_MOUSE_DELTA) {
		if (mi->transformCoordinates) {
			x = matrix[0][0] * rx + matrix[0][1] * ry;
			y = matrix[1][0] * rx + matrix[1][1] * ry;
		} else {
			x = rx;
			y = ry;
		}
		KdMouseAccelerate(pKdPointer, &x, &y);
		xE.u.keyButtonPointer.pad1 = 1;
	} else {
		if (mi->transformCoordinates) {
			x = matrix[0][0] * rx + matrix[0][1] * ry +
			    matrix[0][2];
			y = matrix[1][0] * rx + matrix[1][1] * ry +
			    matrix[1][2];
		} else {
			x = rx;
			y = ry;
		}
		xE.u.keyButtonPointer.pad1 = 0;
	}
	xE.u.keyButtonPointer.time = ms;
	xE.u.keyButtonPointer.rootX = x;
	xE.u.keyButtonPointer.rootY = y;

	xE.u.u.type = MotionNotify;
	xE.u.u.detail = 0;
	KdHandleMouseEvent(mi, &xE);

	buttons = flags;

	for (button = KD_BUTTON_1, n = 0; button <= KD_BUTTON_5;
	     button <<= 1, n++) {
		if ((mi->buttonState & button) ^ (buttons & button)) {
			if (buttons & button) {
				Press(mi, n);
			} else {
				Release(mi, n);
			}
		}
	}
	mi->buttonState = buttons;
}

void KdEnqueueMotionEvent(KdMouseInfo * mi, int x, int y)
{
	xEvent xE;

	CARD32 ms;

	ms = GetTimeInMillis();

	xE.u.u.type = MotionNotify;
	xE.u.keyButtonPointer.time = ms;
	xE.u.keyButtonPointer.rootX = x;
	xE.u.keyButtonPointer.rootY = y;

	KdHandleMouseEvent(mi, &xE);
}

void
KdBlockHandler(int screen, pointer blockData, pointer timeout, pointer readmask)
{
	KdMouseInfo *mi;

	int myTimeout = 0;

	for (mi = kdMouseInfo; mi; mi = mi->next) {
		if (mi->timeoutPending) {
			int ms;

			ms = mi->emulationTimeout - GetTimeInMillis();
			if (ms < 1)
				ms = 1;
			if (ms < myTimeout || myTimeout == 0)
				myTimeout = ms;
		}
	}
	/* if we need to poll for events, do that */
	if (kdOsFuncs->pollEvents) {
		(*kdOsFuncs->pollEvents) ();
		myTimeout = 20;
	}
	if (myTimeout > 0)
		AdjustWaitForDelay(timeout, myTimeout);
}

void
KdWakeupHandler(int screen,
		pointer data, unsigned long lresult, pointer readmask)
{
	int result = (int)lresult;

	fd_set *pReadmask = (fd_set *) readmask;

	int i;

	KdMouseInfo *mi;

	if (kdInputEnabled && result > 0) {
		for (i = 0; i < kdNumInputFds; i++)
			if (FD_ISSET(kdInputFds[i].fd, pReadmask)) {
				KdBlockSigio();
				(*kdInputFds[i].read) (kdInputFds[i].fd,
						       kdInputFds[i].closure);
				KdUnblockSigio();
			}
	}
	for (mi = kdMouseInfo; mi; mi = mi->next) {
		if (mi->timeoutPending) {
			if ((long)(GetTimeInMillis() - mi->emulationTimeout) >=
			    0) {
				mi->timeoutPending = FALSE;
				KdBlockSigio();
				KdReceiveTimeout(mi);
				KdUnblockSigio();
			}
		}
	}
	if (kdSwitchPending)
		KdProcessSwitch();
}

#define KdScreenOrigin(pScreen) (&(KdGetScreenPriv(pScreen)->screen->origin))

static Bool KdCursorOffScreen(ScreenPtr * ppScreen, int *x, int *y)
{
	ScreenPtr pScreen = *ppScreen;

	ScreenPtr pNewScreen;

	int n;

	int dx, dy;

	int best_x, best_y;

	int n_best_x, n_best_y;

	CARD32 ms;

	if (kdDisableZaphod || screenInfo.numScreens <= 1)
		return FALSE;

	if (0 <= *x && *x < pScreen->width && 0 <= *y && *y < pScreen->height)
		return FALSE;

	ms = GetTimeInMillis();
	if (kdOffScreen && (int)(ms - kdOffScreenTime) < 1000)
		return FALSE;
	kdOffScreen = TRUE;
	kdOffScreenTime = ms;
	n_best_x = -1;
	best_x = 32767;
	n_best_y = -1;
	best_y = 32767;
	for (n = 0; n < screenInfo.numScreens; n++) {
		pNewScreen = screenInfo.screens[n];
		if (pNewScreen == pScreen)
			continue;
		dx = KdScreenOrigin(pNewScreen)->x - KdScreenOrigin(pScreen)->x;
		dy = KdScreenOrigin(pNewScreen)->y - KdScreenOrigin(pScreen)->y;
		if (*x < 0) {
			if (dx <= 0 && -dx < best_x) {
				best_x = -dx;
				n_best_x = n;
			}
		} else if (*x >= pScreen->width) {
			if (dx >= 0 && dx < best_x) {
				best_x = dx;
				n_best_x = n;
			}
		}
		if (*y < 0) {
			if (dy <= 0 && -dy < best_y) {
				best_y = -dy;
				n_best_y = n;
			}
		} else if (*y >= pScreen->height) {
			if (dy >= 0 && dy < best_y) {
				best_y = dy;
				n_best_y = n;
			}
		}
	}
	if (best_y < best_x)
		n_best_x = n_best_y;
	if (n_best_x == -1)
		return FALSE;
	pNewScreen = screenInfo.screens[n_best_x];

	if (*x < 0)
		*x += pNewScreen->width;
	if (*y < 0)
		*y += pNewScreen->height;

	if (*x >= pScreen->width)
		*x -= pScreen->width;
	if (*y >= pScreen->height)
		*y -= pScreen->height;

	*ppScreen = pNewScreen;
	return TRUE;
}

static void KdCrossScreen(ScreenPtr pScreen, Bool entering)
{
	if (entering)
		KdEnableScreen(pScreen);
	else
		KdDisableScreen(pScreen);
}

static int KdCurScreen;		/* current event screen */

static void KdWarpCursor(ScreenPtr pScreen, int x, int y)
{
	KdBlockSigio();
	KdCurScreen = pScreen->myNum;
	miPointerWarpCursor(pScreen, x, y);
	KdUnblockSigio();
}

miPointerScreenFuncRec kdPointerScreenFuncs = {
	KdCursorOffScreen,
	KdCrossScreen,
	KdWarpCursor
};

void ProcessInputEvents()
{
	mieqProcessInputEvents();
	miPointerUpdate();
	if (kdSwitchPending)
		KdProcessSwitch();
	KdCheckLock();
}
